package org.opens.lambda.default_method;

import org.junit.Test;

/**
 * 简介:
 *      1. 例如在jdk1.0时有一个类有若干个方法, 很多开源框架对List接口进行了实现, jdk1.8之前该接口的变化不大, 但是在jdk1.8开
 *      始加入了使List生成Stream的方法-> Stream<E> stream(), 此时所有实现了这个接口的类都需要重构, 否则就无法升级, 这会导致
 *      非常高的成本; 而默认方法的加入使这些开源框架无需重构就可以实现升级jdk1.8, 也就是说, 默认方法是不需要实现的, 直接可以
 *      继承.
 *      2. List接口的stream方法是一个非常好的例子.
 *      3. 另一种是静态默认方法, 可以直接通过接口名进行调用的方法, 就是在默认方法之前加static即可, Function接口是一个非常
 *      好的例子.
 *      4. 另一种错误的观点就是加入了静态默认方法之后, 接口变的和抽象类特别像, 其实是有区别的.
 *          - 接口可以多实现, 但是抽象类会占用累的单继承.
 *          - 可以说默认方法的引入打破了单继承.
 */
public class DefaultMethod {

    private interface A {

        int size();

        /**
         * 说明:
         *      1. 可以发现, 经默认方法中可以调用实例方法.
         */
        default boolean isEmpty() {
            return size() == 0;
        }

        //但是在静态默认方法中就会报错, 这个就像实例方法和静态方法的关系一样.
        /*static default boolean isEmpty2() {
            return size() == 0;
        }*/

    }

    @Test
    public void test1() {
        A a = () -> 10;
        System.out.println(a.isEmpty());
        System.out.println(a.size());
    }

    /**
     * 功能:
     *      测试重载方法传入null值时哪个会被执行.
     * 输出:
     *      I'm int[].
     *      I'm Object.
     * 说明:
     *      1. java官方对这种情况的执行做出了回答:
     *          - 当java不能确定一个值的类型时, 总是尝试精确化这个参数的类型, 相比于Object, int[]类型更精确, 所以直接传
     *          入null执行的是org.opens.lambda.default_method.DefaultMethod.B#say(int[])方法, 但是在将int[]赋值给Object
     *          之后, java会认为这个参数是Object类型, 所以执行了org.opens.lambda.default_method.DefaultMethod.B#say(java
     *          .lang.Object)方法.
     */
    @Test
    public void test2() {
        B b = new B();
        b.say(null);

        int[] arr = null;
        Object obj = arr;
        b.say(obj);
    }

    private class B {

        public void say(Object o) {
            System.out.println("I'm Object.");
        }

        public void say(int[] arr) {
            System.out.println("I'm int[].");
        }

    }

    private interface C {
        default void hello() {
            System.out.println("I'm C");
        }
    }

    private interface D extends C {
        @Override
        default void hello() {
            System.out.println("I'm D");
        }
    }

    private static class E implements C, D {

    }

    private static class F implements C {
        @Override
        public void hello() {
            System.out.println("I'm F");
        }
    }

    /**
     * 功能:
     *      测试引入默认方法之后哪个方法的优先级最高.
     * 输出:
     *      I'm D
     *      I'm F
     * 说明:
     *      1. 多重重写方法的三大原则:
     *          - 类的方法最优先;
     *          - 就近原则, 执行距离最近的父类中的方法(但是只能有一个, 引入默认方法后可能会出现多个了).
     *          - 如果前两种情况还没有解决困惑, 则你必须重写这个方法才能通过编译.
     *              1. 相当于是放弃使用父类中的方法, 或者是选择性的执行父类的方法.
     */
    @Test
    public void test4() {
        E e = new E();
        e.hello();
        F f = new F();
        f.hello();
    }

    private interface G {
        default void hello() {
            System.out.println("I'm G");
        }
    }

    private interface H{
        default void hello() {
            System.out.println("I'm H");
        }
    }

    private static class I implements G, H {

        //如果不重写这个方法编译器会报错.
        @Override
        public void hello() {
            System.out.println("I'm I");
            G.super.hello();
            H.super.hello();
        }

    }

    /**
     * 功能:
     *      I接口的hello方法中可以自定义进行输出, 或者使用super关键字选择性的执行父类的方法.
     * 输出:
     *      I'm I
     *      I'm G
     *      I'm H
     * 说明:
     *      1. 要执行父类的方法还是自己的方法现在全是自己说了算, 要执行哪个获取哪个.
     */
    @Test
    public void test6() {
        I s = new I();
        s.hello();
    }

}
